/*
 * Copyright 2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.nanhill.commons.id.uuid;

import java.lang.reflect.InvocationTargetException;

import org.nanhill.commons.id.IdentifierGenerator;
import org.nanhill.commons.id.uuid.clock.OverClockedException;
import org.nanhill.commons.id.uuid.state.Node;
import static org.nanhill.commons.id.uuid.Constants.*;

/**
 * Class is responsible for generating version 1 UUID's per RFC 4122. This class
 * attempts to locate the machine's node identifier first by attempting to
 * loading the properties file UUID.properties from the system classpath. If the
 * UUID.properties file does not exist then the generator will create a node
 * identifier from random information as defined in the <a
 * href="ftp://ftp.rfc-editor.org/in-notes/rfc4122.txt">RFC 4122: A Universally
 * Unique IDentifier (UUID) URN Namespace</a>.
 * 
 * @author Commons-Id team
 * @version $Revision: 1.1 $ $Date: 2006/09/20 06:55:58 $
 * 
 */

public final class VersionOneGenerator implements IdentifierGenerator<UUID> {

	/** Positions 10-16: Length of node bytes */
	protected static final int NODE_ID_BYTE_LENGTH = 6;

	/** Position 8: CLOCK_SEQ_LOW byte */
	private static final int CLOCK_HI_VARIANT_BYTE8 = 8;

	/** Position 9: CLOCK_SEQ_HIGH and VARIANT byte */
	private static final int CLOCK_LOW_BYTE9 = 9;

	/** Positions 10-16: Start of node bytes */
	private static final int NODE_ID_BYTE10 = 10;

	/** The NodeManager implementation */
	private NodeManager manager;

	/**
	 * Singleton instance such that only one instance is accessing the static
	 * fields at any time.
	 */
	private static VersionOneGenerator generator;

	/**
	 * <p>
	 * Private singleton constructor.
	 * </p>
	 * 
	 * @throws NoSuchMethodException
	 *             exception instantiating from DiscoverClass.
	 * @throws InvocationTargetException
	 *             exception instantiating from DiscoverClass.
	 * @throws IllegalAccessException
	 *             exception instantiating from DiscoverClass.
	 * @throws InstantiationException
	 *             exception instantiating from DiscoverClass.
	 */
	private VersionOneGenerator() throws NoSuchMethodException,
			InvocationTargetException, IllegalAccessException,
			InstantiationException {
		super();
		manager = new NodeManagerImpl();
	}

	/**
	 * <p>
	 * Returns the singleton instance of the version one UUID generator.
	 * </p>
	 * 
	 * @return the singleton instance of the version one UUID generator.
	 * @throws NoSuchMethodException
	 *             exception instantiating from DiscoverClass.
	 * @throws InvocationTargetException
	 *             exception instantiating from DiscoverClass.
	 * @throws IllegalAccessException
	 *             exception instantiating from DiscoverClass.
	 * @throws InstantiationException
	 *             exception instantiating from DiscoverClass.
	 */
	public static VersionOneGenerator getInstance()
			throws NoSuchMethodException, InvocationTargetException,
			IllegalAccessException, InstantiationException {
		if (generator == null) {
			generator = new VersionOneGenerator();
		}
		return generator;
	}

	/**
	 * @see org.apache.commons.id.IdentifierGenerator#nextIdentifier()
	 */
	public UUID nextIdentifier() {
		return nextUUID();
	}

	/**
	 * <p>
	 * Returns a new version 1 UUID. The method acts upons static variables and
	 * so should be sychronized.
	 * </p>
	 * 
	 * @return Returns a new version 1 UUID.
	 */
	public synchronized UUID nextUUID() {
		byte[] rawUUID = new byte[UUID_BYTE_LENGTH];
		long time = 0;
		short clockSq = 0;
		Node node = manager.currentNode();
		while (time < 1) {
			try {
				manager.lockNode(node);
				time = node.getUUIDTime();
				clockSq = node.getClockSequence();
				System.arraycopy(node.getNodeIdentifier(), 0, rawUUID,
						NODE_ID_BYTE10, NODE_ID_BYTE_LENGTH);
				manager.releaseNode(node);
			} catch (OverClockedException e) {
				node = manager.nextAvailableNode();
			} finally {
				manager.releaseNode(node);
			}
		}
		byte[] timeBytes = Bytes.toBytes(time);
		// Copy time low
		System.arraycopy(timeBytes, TIME_LOW_TS_POS, rawUUID,
				TIME_LOW_START_POS, TIME_LOW_BYTE_LEN);
		// Copy time mid
		System.arraycopy(timeBytes, TIME_MID_TS_POS, rawUUID,
				TIME_MID_START_POS, TIME_MID_BYTE_LEN);
		// Copy time hi
		System.arraycopy(timeBytes, TIME_HI_TS_POS, rawUUID, TIME_HI_START_POS,
				TIME_HI_BYTE_LEN);
		// Set version
		rawUUID[6] |= 0x10; // 0001 0000
		// Set clock sequence
		rawUUID[CLOCK_HI_VARIANT_BYTE8] = (byte) ((clockSq & 0x3F00) >>> 8);
		rawUUID[CLOCK_HI_VARIANT_BYTE8] |= 0x80;
		rawUUID[CLOCK_LOW_BYTE9] = (byte) (clockSq & 0xFF);

		return new UUID(rawUUID);
	}
}
